package storagetov2

import (
	"testing"
	"time"

	v2 "github.com/stackrox/rox/generated/api/v2"
	"github.com/stackrox/rox/generated/storage"
	"github.com/stackrox/rox/pkg/protoassert"
	"github.com/stackrox/rox/pkg/protocompat"
	"github.com/stretchr/testify/assert"
)

const (
	cveName    = "CVE-2025-23459"
	cveSummary = "Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') vulnerability in NotFound NS Simple Intro Loader allows Reflected XSS. This issue affects NS Simple Intro Loader: from n/a through 2.2.3."
	cveLink    = "https://nvd.nist.gov/vuln/detail/CVE-2025-23459"
)

var (
	publishedOn     = time.Date(2025, time.March, 26, 0, 0, 0, 0, time.UTC)
	firstOccurrence = time.Date(2025, time.March, 26, 12, 34, 56, 789012345, time.UTC)
	lastModified    = time.Date(2025, time.March, 27, 0, 0, 0, 0, time.UTC)

	storageCVSSV3 = &storage.CVSSV3{
		Vector:             "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:L",
		AttackVector:       storage.CVSSV3_ATTACK_NETWORK,
		AttackComplexity:   storage.CVSSV3_COMPLEXITY_LOW,
		PrivilegesRequired: storage.CVSSV3_PRIVILEGE_NONE,
		UserInteraction:    storage.CVSSV3_UI_REQUIRED,
		Scope:              storage.CVSSV3_CHANGED,
		Confidentiality:    storage.CVSSV3_IMPACT_LOW,
		Integrity:          storage.CVSSV3_IMPACT_LOW,
		Availability:       storage.CVSSV3_IMPACT_LOW,
		Score:              7.1,
		Severity:           storage.CVSSV3_HIGH,
	}
	storageVirtualMachineTestVuln = &storage.VirtualMachineVulnerability{
		CveBaseInfo: &storage.VirtualMachineCVEInfo{
			Cve:          cveName,
			Summary:      cveSummary,
			Link:         cveLink,
			PublishedOn:  protocompat.ConvertTimeToTimestampOrNil(&publishedOn),
			CreatedAt:    protocompat.ConvertTimeToTimestampOrNil(&firstOccurrence),
			LastModified: protocompat.ConvertTimeToTimestampOrNil(&lastModified),
			CvssMetrics: []*storage.CVSSScore{
				{
					Source: storage.Source_SOURCE_UNKNOWN,
					Url:    "some-url",
					CvssScore: &storage.CVSSScore_Cvssv3{
						Cvssv3: storageCVSSV3,
					},
				},
			},
		},
	}
	storageVirtualMachineTestVulnWithFixedBy = &storage.VirtualMachineVulnerability{
		CveBaseInfo: &storage.VirtualMachineCVEInfo{
			Cve:          cveName,
			Summary:      cveSummary,
			Link:         cveLink,
			PublishedOn:  protocompat.ConvertTimeToTimestampOrNil(&publishedOn),
			CreatedAt:    protocompat.ConvertTimeToTimestampOrNil(&firstOccurrence),
			LastModified: protocompat.ConvertTimeToTimestampOrNil(&lastModified),
			CvssMetrics: []*storage.CVSSScore{
				{
					Source: storage.Source_SOURCE_UNKNOWN,
					Url:    "some-url",
					CvssScore: &storage.CVSSScore_Cvssv3{
						Cvssv3: storageCVSSV3,
					},
				},
			},
			Advisory: &storage.VirtualMachineAdvisory{
				Name: cveName,
				Link: cveLink,
			},
			Epss: &storage.VirtualMachineEPSS{
				EpssProbability: .42,
				EpssPercentile:  .84,
			},
		},
		Severity: storage.VulnerabilitySeverity_IMPORTANT_VULNERABILITY_SEVERITY,
		SetFixedBy: &storage.VirtualMachineVulnerability_FixedBy{
			FixedBy: "fix version",
		},
	}
	v2CVSSV3 = &v2.CVSSV3{
		Vector:             "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:L",
		AttackVector:       v2.CVSSV3_ATTACK_NETWORK,
		AttackComplexity:   v2.CVSSV3_COMPLEXITY_LOW,
		PrivilegesRequired: v2.CVSSV3_PRIVILEGE_NONE,
		UserInteraction:    v2.CVSSV3_UI_REQUIRED,
		Scope:              v2.CVSSV3_CHANGED,
		Confidentiality:    v2.CVSSV3_IMPACT_LOW,
		Integrity:          v2.CVSSV3_IMPACT_LOW,
		Availability:       v2.CVSSV3_IMPACT_LOW,
		Score:              7.1,
		Severity:           v2.CVSSV3_HIGH,
	}
	v2VirtualMachineTestVuln = &v2.EmbeddedVulnerability{
		Cve:                   cveName,
		Summary:               cveSummary,
		Link:                  cveLink,
		PublishedOn:           protocompat.ConvertTimeToTimestampOrNil(&publishedOn),
		LastModified:          protocompat.ConvertTimeToTimestampOrNil(&lastModified),
		FirstSystemOccurrence: protocompat.ConvertTimeToTimestampOrNil(&firstOccurrence),
		CvssMetrics: []*v2.CVSSScore{
			{
				Source: v2.Source_SOURCE_UNKNOWN,
				Url:    "some-url",
				Cvssv3: v2CVSSV3,
			},
		},
	}
	v2VirtualMachineTestVulnWithFixedBy = &v2.EmbeddedVulnerability{
		Cve:                   cveName,
		Summary:               cveSummary,
		Link:                  cveLink,
		PublishedOn:           protocompat.ConvertTimeToTimestampOrNil(&publishedOn),
		LastModified:          protocompat.ConvertTimeToTimestampOrNil(&lastModified),
		FirstSystemOccurrence: protocompat.ConvertTimeToTimestampOrNil(&firstOccurrence),
		CvssMetrics: []*v2.CVSSScore{
			{
				Source: v2.Source_SOURCE_UNKNOWN,
				Url:    "some-url",
				Cvssv3: v2CVSSV3,
			},
		},
		Advisory: &v2.Advisory{
			Name: cveName,
			Link: cveLink,
		},
		Severity: v2.VulnerabilitySeverity_IMPORTANT_VULNERABILITY_SEVERITY,
		SetFixedBy: &v2.EmbeddedVulnerability_FixedBy{
			FixedBy: "fix version",
		},
		Epss: &v2.EPSS{
			EpssProbability: .42,
			EpssPercentile:  .84,
		},
	}
)

func TestVirtualMachineVulnerabilities(t *testing.T) {
	tests := []struct {
		name     string
		input    []*storage.VirtualMachineVulnerability
		expected []*v2.EmbeddedVulnerability
	}{
		{
			name:     "nil input",
			input:    nil,
			expected: nil,
		},
		{
			name:     "vector without nil item",
			input:    []*storage.VirtualMachineVulnerability{storageVirtualMachineTestVuln},
			expected: []*v2.EmbeddedVulnerability{v2VirtualMachineTestVuln},
		},
		{
			name:     "nil items in input vector are skipped",
			input:    []*storage.VirtualMachineVulnerability{nil, storageVirtualMachineTestVuln, nil},
			expected: []*v2.EmbeddedVulnerability{v2VirtualMachineTestVuln},
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(it *testing.T) {
			result := VirtualMachineVulnerabilities(tc.input)
			protoassert.SlicesEqual(it, tc.expected, result)
		})
	}
}

func TestVirtualMachineVulnerability(t *testing.T) {
	tests := []struct {
		name     string
		input    *storage.VirtualMachineVulnerability
		expected *v2.EmbeddedVulnerability
	}{
		{
			name:     "nil input",
			input:    nil,
			expected: nil,
		},
		{
			name:     "vulnerability without cve info",
			input:    &storage.VirtualMachineVulnerability{},
			expected: &v2.EmbeddedVulnerability{},
		},
		{
			name:     "Vulnerability with CVE info",
			input:    storageVirtualMachineTestVuln,
			expected: v2VirtualMachineTestVuln,
		},
		{
			name:     "Vulnerability with fixedBy info",
			input:    storageVirtualMachineTestVulnWithFixedBy,
			expected: v2VirtualMachineTestVulnWithFixedBy,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(it *testing.T) {
			result := VirtualMachineVulnerability(tc.input)
			protoassert.Equal(it, tc.expected, result)
		})
	}
}

func TestConvertVulnerabilitySeverity(t *testing.T) {
	tests := map[string]struct {
		input    storage.VulnerabilitySeverity
		expected v2.VulnerabilitySeverity
	}{
		"UNKNOWN": {
			input:    storage.VulnerabilitySeverity_UNKNOWN_VULNERABILITY_SEVERITY,
			expected: v2.VulnerabilitySeverity_UNKNOWN_VULNERABILITY_SEVERITY,
		},
		"LOW": {
			input:    storage.VulnerabilitySeverity_LOW_VULNERABILITY_SEVERITY,
			expected: v2.VulnerabilitySeverity_LOW_VULNERABILITY_SEVERITY,
		},
		"MODERATE": {
			input:    storage.VulnerabilitySeverity_MODERATE_VULNERABILITY_SEVERITY,
			expected: v2.VulnerabilitySeverity_MODERATE_VULNERABILITY_SEVERITY,
		},
		"IMPORTANT": {
			input:    storage.VulnerabilitySeverity_IMPORTANT_VULNERABILITY_SEVERITY,
			expected: v2.VulnerabilitySeverity_IMPORTANT_VULNERABILITY_SEVERITY,
		},
		"CRITICAL": {
			input:    storage.VulnerabilitySeverity_CRITICAL_VULNERABILITY_SEVERITY,
			expected: v2.VulnerabilitySeverity_CRITICAL_VULNERABILITY_SEVERITY,
		},
		"Default": {
			input:    storage.VulnerabilitySeverity(-1),
			expected: v2.VulnerabilitySeverity_UNKNOWN_VULNERABILITY_SEVERITY,
		},
	}

	for name, tc := range tests {
		t.Run(name, func(it *testing.T) {
			result := convertVulnerabilitySeverity(tc.input)
			assert.Equal(it, tc.expected, result)
		})
	}
}

func TestScoreVersions(t *testing.T) {
	tests := map[string]struct {
		input    []*storage.CVSSScore
		expected []*v2.CVSSScore
	}{
		"nil input": {
			input:    nil,
			expected: nil,
		},
		"empty input": {
			input:    []*storage.CVSSScore{},
			expected: []*v2.CVSSScore{},
		},
		"nil score is not propagated, normal score is": {
			input: []*storage.CVSSScore{
				nil,
				{
					Source: storage.Source_SOURCE_OSV,
					Url:    "some-url",
					CvssScore: &storage.CVSSScore_Cvssv3{
						Cvssv3: storageCVSSV3,
					},
				},
			},
			expected: []*v2.CVSSScore{
				{
					Source: v2.Source_SOURCE_OSV,
					Url:    "some-url",
					Cvssv3: v2CVSSV3,
				},
			},
		},
	}

	for name, tc := range tests {
		t.Run(name, func(it *testing.T) {
			result := ScoreVersions(tc.input)
			protoassert.SlicesEqual(it, tc.expected, result)
		})
	}
}

func TestScoreVersion(t *testing.T) {
	tests := map[string]struct {
		input    *storage.CVSSScore
		expected *v2.CVSSScore
	}{
		"nil input": {
			input:    nil,
			expected: nil,
		},
		"cvssv2 is not propagated": {
			input: &storage.CVSSScore{
				Source: storage.Source_SOURCE_UNKNOWN,
				Url:    "score-url-1",
				CvssScore: &storage.CVSSScore_Cvssv2{
					Cvssv2: &storage.CVSSV2{
						Vector:              "AV:N/AC:L/Au:N/C:P/I:P/A:P",
						AttackVector:        storage.CVSSV2_ATTACK_NETWORK,
						AccessComplexity:    storage.CVSSV2_ACCESS_LOW,
						Authentication:      storage.CVSSV2_AUTH_NONE,
						Confidentiality:     storage.CVSSV2_IMPACT_PARTIAL,
						Integrity:           storage.CVSSV2_IMPACT_PARTIAL,
						Availability:        storage.CVSSV2_IMPACT_PARTIAL,
						ExploitabilityScore: 10.0,
						ImpactScore:         6.4,
						Score:               7.5,
						Severity:            storage.CVSSV2_HIGH,
					},
				},
			},
			expected: &v2.CVSSScore{
				Source: v2.Source_SOURCE_UNKNOWN,
				Url:    "score-url-1",
			},
		},
		"cvssv3 is propagated": {
			input: &storage.CVSSScore{
				Source: storage.Source_SOURCE_NVD,
				Url:    "score-url-2",
				CvssScore: &storage.CVSSScore_Cvssv3{
					Cvssv3: storageCVSSV3,
				},
			},
			expected: &v2.CVSSScore{
				Source: v2.Source_SOURCE_NVD,
				Url:    "score-url-2",
				Cvssv3: v2CVSSV3,
			},
		},
	}

	for name, tc := range tests {
		t.Run(name, func(it *testing.T) {
			result := ScoreVersion(tc.input)
			protoassert.Equal(it, tc.expected, result)
		})
	}

}

func TestConvertCVSSScoreSource(t *testing.T) {
	tests := map[string]struct {
		input    storage.Source
		expected v2.Source
	}{
		"UNKNOWN": {
			input:    storage.Source_SOURCE_UNKNOWN,
			expected: v2.Source_SOURCE_UNKNOWN,
		},
		"REDHAT": {
			input:    storage.Source_SOURCE_RED_HAT,
			expected: v2.Source_SOURCE_RED_HAT,
		},
		"OSV": {
			input:    storage.Source_SOURCE_OSV,
			expected: v2.Source_SOURCE_OSV,
		},
		"NVD": {
			input:    storage.Source_SOURCE_NVD,
			expected: v2.Source_SOURCE_NVD,
		},
		"Default": {
			input:    storage.Source(-1),
			expected: v2.Source_SOURCE_UNKNOWN,
		},
	}

	for name, tc := range tests {
		t.Run(name, func(it *testing.T) {
			result := convertCVSSScoreSource(tc.input)
			assert.Equal(it, tc.expected, result)
		})
	}
}

func TestCvssV3(t *testing.T) {
	tests := []struct {
		name     string
		input    *storage.CVSSV3
		expected *v2.CVSSV3
	}{
		{
			name:     "nil input",
			input:    nil,
			expected: nil,
		},
		{
			name:     "complete cvss v3",
			input:    storageCVSSV3,
			expected: v2CVSSV3,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := CvssV3(tt.input)
			protoassert.Equal(t, tt.expected, result)
		})
	}
}
